﻿using System;

namespace RSAID
{
    public sealed class ValidatedRSAID
    {
        public readonly string IDNumber;
        public readonly bool IsValid;
        public readonly DateTime? DateOfBirth;
        public readonly Gender? Gender;
        public readonly bool? IsCitizen;

        public ValidatedRSAID(string idNumber)
        {
            IDNumber = idNumber;
            DateOfBirth = null;
            Gender = null;
            IsCitizen = false;
            IsValid = false;

            // empty string or not 13 digits? then invalid - return.
            if (string.IsNullOrEmpty(IDNumber) || IDNumber.Length != 13)
                return;

            int i;

            // ensure the number contains 13 digits, so we can avoid the expense of a try-catch block later on
            for (i = 0; i < 13; i++)
                if (idNumber[i] < '0' || idNumber[i] > '9')
                    return;

            i = 0;

            // Get all the digits 
            var yy = ((idNumber[i++] - '0') * 10) + (idNumber[i++] - '0');
            var mm = ((idNumber[i++] - '0') * 10) + (idNumber[i++] - '0');
            var dd = ((idNumber[i++] - '0') * 10) + (idNumber[i++] - '0');
            var genderValue = (idNumber[i++] - '0');
            i += 3; // series. 3 digits
            var citizenValue = (idNumber[i++] - '0');
            i++; // uniform. 1 digits.
            int checkDigit = (idNumber[i] - '0');

            // Derive member values
            Gender = (genderValue < 5 ? RSAID.Gender.Female : RSAID.Gender.Male);
            IsCitizen = citizenValue == 0;

            // detect invalid date of birth digits
            if (yy < 0 || yy > 99 || mm < 1 || mm > 12 || dd < 1 || dd > 31 ||
               (dd > 30 && (mm == 4 || mm == 9 || mm == 6 || mm == 11)))
                return;

            var currentYear = DateTime.Now.Year % 100;
            yy += (yy > currentYear ? 1900 : 2000);

            // detect invalid date for febrauary 
            if (mm == 2 && dd > (28 + (DateTime.IsLeapYear(yy) ? 1 : 0)))
                return;

            DateOfBirth = new DateTime(yy, mm, dd);

            var check = CalculateControlDigit(IDNumber);

            IsValid = checkDigit == check;
        }

        private static Int32 CalculateControlDigit(string idNumber)
        {
            var oddDigits = 0;
            var evenDigits = 0;
            var evenCheck = 0;

            for (var i = 0; i < 12; i++)
            {
                var digit = idNumber[i] - '0';

                if (i % 2 == 0)
                    oddDigits += digit;
                else
                    evenDigits = (evenDigits * 10) + digit;
            }

            for (evenDigits *= 2; evenDigits > 0; evenDigits = evenDigits / 10)
                evenCheck += evenDigits % 10;

            return 10 - ((oddDigits + evenCheck) % 10);
        }
    }
}